package org.concepts.java.antlr.validator;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Properties;

import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.CommonTreeNodeStream;

/**
 * Using {@link Validator#validate(Properties, String)} method user can validate 
 * that  a collection of properties follow the validation expression. E.g.
 * <p/>
 * 
 * <pre>
 * Properties
 * -----------
 *  
 * name = foo
 * first = 35
 * 
 * Expression
 * -----------
 * !null($name) && $first > 30
 * </pre>
 * 
 * In above, the validation passes, as name is not null and age is greater than
 * 30.
 * <p/> 
 * Validator expression provides the following operators and functions.
 * <p/>
 * 
 * <pre>
 * - Math operators.
 *     - '+', '-', '*', '/', '>', '<', '>=', '<=', '==', '!='
 * - Logical operators.
 *     - 'AND' or '&&', 'OR' or '||', 'NOT' or '!', '(...)'
 * - functions: 
 *     - null(), matches(), substring()
 *     - All functions return a boolean and are used along or in combination
 *       with logical operators. 
 * </pre>
 * 
 * The operator precedence follows the same rule as Java operators. See
 * http://java.sun.com/docs/books/tutorial/java/nutsandbolts/operators.html
 * <p/>
 * 
 * The operators are listed below in decreasing order of precedence. Ones
 * with the same precedence are in the same line. Operators with the same 
 * precedence are evaluated left to right.  
 * 
 * <pre>
 *   '*', '/'
 *   '+', '-'
 *   '>', '<', '>=', '<='
 *   '==' '!='
 *   '&&'
 *   '||'
 * </pre>
 *     
 * @author vvvv
 * 
 */
public class Validator {

  /**
   * Validates the data using the expression passed in.
   * 
   * @param data
   *          Data to validate. Can be null. Data is an ordered list
   *          of variables (name value pairs). The key is the name of the
   *          variable. A variable can reference another variable. It's
   *          computed using the same rules as expression. 
   * @param expression
   *          Expression to use for validation. Can be null.
   * @return returns true if validation is successful. true is also returned if
   *         null is passed in for the expression.
   */
  public static boolean validate(LinkedHashMap data, String expression) {
    if (expression == null) {
      return true;
    }

    StringBuffer dataVariables = new StringBuffer("\n");
    if (data != null) {
      for (Iterator iterator = data.keySet().iterator(); iterator.hasNext();) {
        String key = (String) iterator.next();
        dataVariables.append(key + " = " + data.get(key) + ";\n");
      }
    }

    String validationQuery = dataVariables + "\n" + expression + "\n";
    System.out.println("Validation Query [" + validationQuery + "]");

    try {
      CommonTree tree = (CommonTree) new ValParser(new CommonTokenStream(
          new ValLexer(new ANTLRStringStream(validationQuery)))).validator()
          .getTree();

      System.out.println("\n" + "AST tree : " + tree.toStringTree());

      ValWalker walker = new ValWalker(new CommonTreeNodeStream(tree));
      boolean result = walker.validator();
      System.out.println("result : " + result);
      return result;

    } catch (RecognitionException e) {
      e.printStackTrace();
      throw new RuntimeException(e.getMessage(), e);
    }
  }

}
